home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Sample Code / Snippets / Devices / SCSI Simple Sample / Src / AsyncSCSI.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-03-24  |  19.2 KB  |  511 lines  |  [TEXT/KAHL]

  1. /*                                    AsyncSCSI.c                                    */
  2. /*
  3.  * AsyncSCSI.c
  4.  * Copyright © 1992-93 Apple Computer Inc. All Rights Reserved.
  5.  *
  6.  * Talk to the Macintosh SCSI Manager 4.3 using the "new" interface. This is a
  7.  * synchronous call that executes a single SCSI Command on a device. It will
  8.  * automatically calls Request Sense on errors. Note: this is intended as a self-
  9.  * contained illustration of the Asynchronous SCSI Manager and is (intentionally)
  10.  * inefficient in that it does many "bureaucratic" things that would normally
  11.  * be done once when an application or device driver is initialized.
  12.  *
  13.  * Calling Sequence:
  14.  *        OSErr                AsyncSCSI(
  15.  *                DeviceIdent                scsiDevice,
  16.  *                const SCSI_CommandPtr    scsiCommand,
  17.  *                unsigned short            cmdBlockLength,
  18.  *                Boolean                    writeToDevice,
  19.  *                Ptr                        bufferPtr,
  20.  *                unsigned long            transferSize,
  21.  *                unsigned long            transferQuantum,
  22.  *                SCSI_Sense_Data            *senseDataPtr,
  23.  *                unsigned long            senseDataSize,
  24.  *                unsigned long            completionTimeout,
  25.  *                unsigned short            *stsBytePtr,
  26.  *                unsigned short            *msgBytePtr
  27.  *                unsigned long            *actualTransferCount
  28.  *            );
  29.  * The parameters have the following meaning:
  30.  *
  31.  *    scsiDevice            The SCSI host bus, target, lun that we are talking to.
  32.  *    scsiCommand            The SCSI Command Block (6, 10, or 12 bytes).
  33.  *    cmdBlockLength        The length in bytes of the command block.
  34.  *    writeToDevice        TRUE if this command writes to the device. FALSE if this
  35.  *                        command reads from the device or does not require a data
  36.  *                        phase.
  37.  *    bufferPtr            The user data buffer for Read/Write commands. It should be
  38.  *                        NULL if a data transfer phase is not used for this command.
  39.  *                        (e.g. for Test Unit Ready).
  40.  *    transferSize        The total number of bytes to transfer to or from the device.
  41.  *    transferQuantum        This is needed to configure the handshake information.
  42.  *                        The following values are appropriate:
  43.  *                        -- Set to zero for a one-shot blind transfer.
  44.  *                            ActualTransferCount is not correctly returned.
  45.  *                        -- Set to one if a polled transfer is needed. This is
  46.  *                            useful for Request Sense or other management requests,
  47.  *                            especially requests with a variable-length record. On
  48.  *                            return, actualTransferCount will have the number of
  49.  *                            bytes transferred..
  50.  *                        -- Set to the blockSize value for a normal, "Blind"
  51.  *                            transfer. This is the normal case for data requests.
  52.  *                            ActualTransferCount will be set to the block size
  53.  *                            times the number of blocks). On error, the actual
  54.  *                            transfer cound may be recovered from the Request
  55.  *                            Sense buffer.
  56.  *                        -- Other values will likely result in errors.
  57.  *    senseDataPtr        If not NULL and the original request failed, this will
  58.  *                        be filled with the result from a Request Sense operation.
  59.  *    senseDataSize        This is the size of the Request Sense data buffer.
  60.  *    completionTimeout    The timeout (in Ticks) for the command. This should be
  61.  *                        short for disks, but must be long for tape devices and
  62.  *                        some setup requests, such as Mode Select.
  63.  *    stsBytePtr            This short is set to the byte returned in the device's
  64.  *                        Status Phase.
  65.  *    msgBytePtr            This short is set to the byte returned in the device's
  66.  *                        Command Complete message.
  67.  *    actualTransferCount    This will be set to the number "cycles" through the TIB
  68.  *                        loop. This should equal the number of bytes transferred if
  69.  *                        transferCount is set to one. (Ignored if NULL.)
  70.  * Return codes:
  71.  *    noErr            normal
  72.  *    unimpErr        SCSI Manager 4.3 is not available: the calling function should
  73.  *                    call the "old" (Inside Mac IV) SCSI Manager.
  74.  *    statusErr        Device returned "Check condition" and SCSI Manager was able
  75.  *                    to successfully issue Request Sense. There is data in the
  76.  *                    Sense Record, but you cannot assume that the original request
  77.  *                    succeeded.
  78.  *    paramErr        Could not determine the command length.
  79.  *    scsi...            Other error
  80.  */
  81. #include <GestaltEqu.h>
  82. #include <Memory.h>
  83. #include <Events.h>
  84. #include <Errors.h>
  85. #include "MacSCSICommand.h"
  86. #ifndef TRUE
  87. #define TRUE        1
  88. #define FALSE        0
  89. #endif
  90.  
  91. /*
  92.  * These globals are defined by SCSISimpleSample.h, and are needed only for
  93.  * testing and debugging.
  94.  */
  95. extern Boolean                    gEnableSelectWithATN;
  96. extern Boolean                    gDoDisconnect;
  97. extern Boolean                    gDontDisconnect;
  98.  
  99. static void                     NextFunction(void);        /* For HoldMemory size    */
  100. /*
  101.  * This should be specified at application/driver startup, and not the
  102.  * inefficient "call Gestalt each time" shown here.
  103.  */
  104. static Boolean                    IsVirtualMemoryRunning(void);
  105. #ifndef CLEAR
  106. /*
  107.  * Cheap 'n dirty memory clear routine.
  108.  */
  109. #define CLEAR(record) do {                                \
  110.         register char    *ptr = (char *) &record;        \
  111.         register long    size;                            \
  112.         for (size = sizeof record; size > 0; --size)    \
  113.             *ptr++ = 0;                                    \
  114.     } while (0)
  115.  
  116. #endif
  117.  
  118. /*
  119.  * These are bitmasks for the vmHoldMask variable. A bit is set if its associated
  120.  * memory element has been held in protected (non-paged) memory.
  121.  */
  122. #define kHoldFunction            0x0001                /* AsyncSCSI function code    */
  123. #define kHoldStack                0x0002                /* Local variables            */
  124. #define kHoldUserBuffer            0x0004                /* User data buffer, if any    */
  125. #define kHoldSenseBuffer        0x0008                /* Sense buffer, if any        */
  126. #define kHoldParamBlock            0x0010                /* SCSIExecIOPB                */
  127.  
  128. OSErr                        AsyncSCSI(
  129.         DeviceIdent                scsiDevice,            /* -> Bus/target/LUN        */
  130.         const SCSI_CommandPtr    scsiCommand,        /* The actual scsi command    */
  131.         unsigned short            cmdBlockLength,        /* -> Length of CDB            */
  132.         Boolean                    writeToDevice,        /* TRUE to write            */
  133.         Ptr                        bufferPtr,            /* -> user data buffer        */
  134.         unsigned long            transferSize,        /* How much to transfer        */
  135.         unsigned long            transferQuantum,    /* TIB setup parameter        */
  136.         SCSI_Sense_Data            *senseDataPtr,        /* Request Sense results    */
  137.         unsigned long            senseDataSize,        /* Request Sense data size    */
  138.         unsigned long            completionTimeout,    /* Ticks to wait            */
  139.         unsigned short            *stsBytePtr,        /* <- status phase byte        */
  140.         unsigned short            *msgBytePtr,        /* <- cmd complete messge    */
  141.         unsigned long            *actualTransferCount
  142.     );
  143.  
  144. /*
  145.  * Execute a SCSI command.
  146.  * Returns the final status as noted above.
  147.  */
  148. OSErr
  149. AsyncSCSI(
  150.         DeviceIdent                scsiDevice,            /* -> Bus/target/LUN        */
  151.         const SCSI_CommandPtr    scsiCommand,        /* The actual scsi command    */
  152.         unsigned short            cmdBlockLength,        /* -> Length of CDB            */
  153.         Boolean                    writeToDevice,        /* TRUE to write            */
  154.         Ptr                        bufferPtr,            /* -> user data buffer        */
  155.         unsigned long            transferSize,        /* How much to transfer        */
  156.         unsigned long            transferQuantum,    /* TIB setup parameter        */
  157.         SCSI_Sense_Data            *senseDataPtr,        /* Request Sense results    */
  158.         unsigned long            senseDataSize,        /* Request Sense data size    */
  159.         unsigned long            completionTimeout,    /* Ticks to wait            */
  160.         unsigned short            *stsBytePtr,        /* <- status phase byte        */
  161.         unsigned short            *msgBytePtr,        /* <- cmd complete messge    */
  162.         unsigned long            *actualTransferCount
  163.     )
  164. {
  165.         OSErr                    status;
  166.         SCSIBusInquiryPB        busInquiryPB;
  167.         register SCSIExecIOPB    *execIOPBPtr;
  168. #define PB                        (*execIOPBPtr)
  169.         unsigned long            execIOPBSize;
  170.         /*
  171.          * These two flags are used to record whether the asynchronous SCSI
  172.          * Manager is present on this machine (one flag records whether it is
  173.          * present, the other whether we've tested for presence). This is
  174.          * reasonable for applications. However, this is not sufficient for
  175.          * drivers or other code that may be called before the system has been
  176.          * completely initialized, as the asynchronous SCSI Manager may be
  177.          * installed by a System Extension.
  178.          */
  179.         static Boolean            gHasAsyncSCSIManager;
  180.         static Boolean            gTestedForAsyncSCSIManager;
  181.         /*
  182.          * This receives the Command Complete message. Note that we can store it
  183.          * on the stack since we are calling the SCSI manager synchronously. If
  184.          * you are calling the SCSI Manager asynchronously, this must be in the
  185.          * System Heap or otherwise protected from virtual memory problems. For
  186.          * example, you could allocate it together with the SCSIExecIOPB record.
  187.          */
  188.         unsigned char            scsiMsgBuffer[1];        /* Note: sync req    */
  189.         /*
  190.          * The following parameters are used to manage virtual memory.
  191.          */
  192.         unsigned short            vmHoldMask;
  193.         unsigned long            vmFunctionSize;
  194.         void                    *vmProtectedStackBase;    /* Last local var    */
  195. /*
  196.  * These values are used to compute the size of the stack that we must hold in
  197.  * protected (non-virtual) memory. kSCSIManagerStackEstimate is an estimate.
  198.  */
  199. #define kSCSILocalVariableSize    ( \
  200.         (unsigned long) (((Ptr) &status) - ((Ptr) &vmProtectedStackBase))    \
  201.     )
  202. #define kSCSIManagerStackEstimate 512
  203. #define kSCSIProtectedStackSize (kSCSIManagerStackEstimate + kSCSILocalVariableSize)
  204.  
  205.         status = noErr;
  206.         vmHoldMask = 0;
  207.         /*
  208.          * If the asynchronous SCSI Manager exists, we will allocate a parameter
  209.          * block that will be freed when the function exits (if allocation
  210.          * succeeded). In a real application or driver, this would be allocated
  211.          * once, as part of the per-device initialization.
  212.          */
  213.         execIOPBPtr = NULL;
  214.         /*
  215.          * First, make sure that the asynchronous SCSI Manager has been installed.
  216.          * In an application, this may be done once when the application starts.
  217.          * In a driver, this test must be deferred until the Process Manager
  218.          * is running.
  219.          */
  220.         if (gTestedForAsyncSCSIManager == FALSE) {
  221.             gTestedForAsyncSCSIManager = TRUE;
  222.             gHasAsyncSCSIManager = (
  223.                     NGetTrapAddress(_SCSIAtomic, OSTrap)
  224.                     != NGetTrapAddress(_Unimplemented, OSTrap)
  225.                 );
  226.         }
  227.         if (gHasAsyncSCSIManager == FALSE) {
  228.             status = unimpErr;
  229.             goto exit;
  230.         }
  231.         /*
  232.          * Allocate a parameter block for this bus. In a production application
  233.          * or driver, this would be done, once, along with other initialization.
  234.          * Note that we always clear the parameter block: the SCSI Manager will
  235.          * fail with an error if some fields it expects to be NULL (such as
  236.          * the queue link) are non-NULL.
  237.          */
  238.         CLEAR(busInquiryPB);
  239.         busInquiryPB.scsiPBLength = sizeof busInquiryPB;
  240.         busInquiryPB.scsiFunctionCode = SCSIBusInquiry;
  241.         busInquiryPB.scsiDevice = scsiDevice;
  242.         SCSIAction((SCSI_PB *) &busInquiryPB);
  243.         status = busInquiryPB.scsiResult;
  244.         if (status != noErr)
  245.             goto exit;
  246.         /*
  247.          * Allocate a parameter block for this request using the size that
  248.          * was returned in the busInquiry parameter block.
  249.          */
  250.         execIOPBSize = busInquiryPB.scsiIOpbSize;
  251.         execIOPBPtr = (SCSIExecIOPB *) NewPtrClear(execIOPBSize);
  252.         if (execIOPBPtr == NULL) {
  253.             status = MemError();
  254.             goto exit;
  255.         }
  256.         /*
  257.          * Setup the parameter block for the user's request.
  258.          */
  259.         PB.scsiPBLength = execIOPBSize;
  260.         PB.scsiFunctionCode = SCSIExecIO;
  261.         PB.scsiMessagePtr = scsiMsgBuffer;    /* For the Command Complete Message    */
  262.         PB.scsiMessageLen = 1;
  263.         scsiMsgBuffer[0] = 0xFF;            /* An "illegal" value for debugging    */
  264.         PB.scsiTimeout = completionTimeout;
  265.         PB.scsiDevice = scsiDevice;
  266.         PB.scsiCDBLength = cmdBlockLength;
  267.         /*
  268.          * Copy the command block into the SCSI ExecIO Parameter block to
  269.          * centralize everything for debugging. Also, this is one less thing
  270.          * to have to lock into physical memory.
  271.          */
  272.         BlockMove(scsiCommand, &PB.scsiCDB, PB.scsiCDBLength);
  273.         /*
  274.          * Specify the transfer direction, if any, and setup the other SCSI
  275.          * operation flags. scsiSIMQNoFreeze prevents the SCSI Manager from
  276.          * blocking further operation if an error is detected.
  277.          */
  278.         PB.scsiFlags = scsiSIMQNoFreeze;
  279.         if (bufferPtr == NULL || transferSize == 0)
  280.             PB.scsiFlags |= scsiDirectionNone;
  281.         else {
  282.             /*
  283.              * If the user specified the transfer quantum == 1, select "polled"
  284.              * transfers, otherwise, select "blind."
  285.              */
  286.             PB.scsiTransferType = (transferQuantum == 1)
  287.                         ? scsiTransferPolled
  288.                         : scsiTransferBlind;
  289.             PB.scsiDataPtr = bufferPtr;
  290.             PB.scsiDataLength = transferSize;
  291.             PB.scsiDataType = scsiDataBuffer;
  292.             PB.scsiFlags |= (writeToDevice) ? scsiDirectionOut : scsiDirectionIn;
  293.             if (transferQuantum == 1) {
  294.                 /*
  295.                  * Polled transfer: handshake is ignored.
  296.                  */
  297.                 PB.scsiHandshake[0] = 1;
  298.                 PB.scsiHandshake[1] = 0;
  299.                 /*
  300.                  * For polled transfers, turn off select with attention so
  301.                  * the device doesn't disconnect. This is inefficient, but
  302.                  * may be useful for Request Sense and similar infrequently-
  303.                  * used commands.
  304.                  */
  305.                 PB.scsiIOFlags |= scsiDisableSelectWAtn;
  306.             }
  307.             else if (transferQuantum == 0) {
  308.                 /*
  309.                  * Normal transfer: resynchronize every block.
  310.                  */
  311.                 PB.scsiHandshake[0] = 512;
  312.                 PB.scsiHandshake[1] = 0;
  313.             }
  314.             else if (transferQuantum < 512) {
  315.                 /*
  316.                  * Special case for multi-byte polling.
  317.                  */
  318.                 PB.scsiHandshake[0] = transferQuantum;
  319.                 PB.scsiHandshake[1] = 512 - transferQuantum;
  320.                 PB.scsiHandshake[2] = 0;
  321.             }
  322.             else /* transferQuantum >= 512 */ {
  323.                 /*
  324.                  * Set the handshake to the user-specified value.
  325.                  */
  326.                 PB.scsiHandshake[0] = transferQuantum;
  327.                 PB.scsiHandshake[1] = 0;
  328.             }
  329.         }
  330.         /*
  331.          * Do we support autosense?
  332.          */
  333.         if (senseDataPtr != NULL && senseDataSize >= 5) {
  334.             senseDataPtr->errorCode = 0;
  335.             PB.scsiSensePtr = (Ptr) senseDataPtr;
  336.             PB.scsiSenseLength = senseDataSize;
  337.         }
  338.         else {
  339.             PB.scsiFlags |= scsiDisableAutosense;
  340.         }
  341.         /*
  342.          * Look at the global flags that can be set to configure the asynchronous
  343.          * SCSI Manager - these are for testing, and would typically be set
  344.          * permanently to a particular (device-specific) state by a real application.
  345.          */
  346.         if (gEnableSelectWithATN == FALSE)
  347.             PB.scsiIOFlags |= scsiDisableSelectWAtn;
  348.         if (gDoDisconnect)
  349.             PB.scsiFlags |= scsiDoDisconnect;
  350.         if (gDontDisconnect)
  351.             PB.scsiFlags |= scsiDontDisconnect;
  352.         /*
  353.          * We are now ready to perform the operation. If virtual memory is active
  354.          * however, we must lock down all memory segments that can be potentially
  355.          * "touched" while the SCSI request is being executed. All of this is
  356.          * needed for applications. For drivers, this can generally be ignored as
  357.          * the driver code and driver-specific resources are stored in the System
  358.          * Heap, which is always "held" in physical memory and the Device Manager
  359.          * locks down data buffers when PBRead/PBWrite are called. However, note
  360.          * that if Control or Status calls require data transfers, the driver
  361.          * must explicitly lock the buffer.
  362.          */
  363.         if (IsVirtualMemoryRunning()) {
  364.             /*
  365.              * Virtual memory is active. Lock all of the memory segments that we
  366.              * need in "real" memory (i.e. non-paged pool) for the duration of the
  367.              * call. Since  we need to back out of VM holds if there are errors,
  368.              * we'll use bits in vmHoldMask to record the status of our attempts.
  369.              *
  370.              * Note: in a real application or driver, the user buffers should be
  371.              * held outside of the SCSI Manager code:
  372.              *        HoldMemory(data buffer);
  373.              *        HoldMemory(autosense buffer);
  374.              *        status = CallSCSIManager(...);
  375.              *        UnholdMemory(...);
  376.              *
  377.              * First, hold the MacSCSI function. It starts at AsyncSCSI and
  378.              * extends to the start of the next function. This is marked by a
  379.              * dummy function. The left-margin comments indicate the value
  380.              * of vmHoldCount if the indicated HoldMemory succeeded. This is not
  381.              * needed for drivers.
  382.              */
  383.             vmFunctionSize =
  384.                 (unsigned long) NextFunction - (unsigned long) AsyncSCSI;
  385.             status = HoldMemory(AsyncSCSI, vmFunctionSize);
  386.             if (status == noErr)
  387.                 vmHoldMask |= kHoldFunction;
  388.             if (status == noErr) {
  389.                 /*
  390.                  * Hold a chunk of stack space to call the SCSI Manager and to
  391.                  * protect our local variables. This is always needed, as drivers
  392.                  * can be called from application contexts.
  393.                  */
  394.                 vmProtectedStackBase =
  395.                     (char *) &vmProtectedStackBase - kSCSIManagerStackEstimate;
  396.                 status = HoldMemory(vmProtectedStackBase, kSCSIProtectedStackSize);
  397.                 if (status == noErr)
  398.                     vmHoldMask |= kHoldStack;
  399.             }
  400.             if (status == noErr) {
  401.                 /*
  402.                  * Lock down the parameter block. In this sample, we allocated
  403.                  * the parameter block in the application heap. A driver would
  404.                  * typically allocate it in the System Heap and, hence, would
  405.                  * not require this call.
  406.                  */
  407.                 status = HoldMemory((Ptr) execIOPBPtr, execIOPBSize);
  408.                 if (status == noErr)
  409.                     vmHoldMask |= kHoldParamBlock;
  410.             }        
  411.             if (status == noErr && bufferPtr != NULL) {
  412.                 /*
  413.                  * Lock down the user buffer, if any. In a real-world application
  414.                  * or driver, this would be done before calling the SCSI interface.
  415.                  */
  416.                 status = HoldMemory(bufferPtr, transferSize);
  417.                 if (status != noErr)
  418.                     vmHoldMask |= kHoldStack;
  419.             }
  420.             if (status == noErr && PB.scsiSensePtr != NULL) {
  421.                 /*
  422.                  * Lock down the sense data. A driver would probably allocate one
  423.                  * of these (in a per-transaction buffer) in the System Heap. An
  424.                  * application should lock this before calling the SCSI Manager.
  425.                  */
  426.                 status = HoldMemory(PB.scsiSensePtr, PB.scsiSenseLength);
  427.                 if (status == noErr)
  428.                     vmHoldMask |= kHoldSenseBuffer;
  429.             }
  430.         }
  431.         /*
  432.          * Finally, call the asynchronous SCSI Manager. SCSIAction is synchronous
  433.          * because we did not specify a completion routine.
  434.          */
  435.         if (status == noErr) {
  436.             status = SCSIAction((SCSI_PB *) &PB);
  437.             if (status == noErr)
  438.                 status = PB.scsiResult;
  439.         }
  440.         /*
  441.          * If we held memory, unhold it now.  We ignore UnholdMemory errors:
  442.          * there isn't much we can do about them. Note that this must be
  443.          * done by driver or asynchronous completion routines.
  444.          */
  445. exit:    if ((vmHoldMask & kHoldSenseBuffer) != 0)
  446.             (void) UnholdMemory(PB.scsiSensePtr, PB.scsiSenseLength);
  447.         if ((vmHoldMask & kHoldUserBuffer) != 0)
  448.             (void) UnholdMemory(bufferPtr, transferSize);
  449.         if ((vmHoldMask & kHoldParamBlock) != 0)
  450.             (void) UnholdMemory((Ptr) execIOPBPtr, execIOPBSize);
  451.         if ((vmHoldMask & kHoldStack) != 0)
  452.             (void) UnholdMemory(vmProtectedStackBase, kSCSIProtectedStackSize);
  453.         if ((vmHoldMask & kHoldFunction) != 0)
  454.             (void) UnholdMemory(AsyncSCSI, vmFunctionSize);
  455.         /*
  456.          * Now, look at the result of the operation.
  457.          */
  458.         if (execIOPBPtr != NULL) {
  459.             /*
  460.              * Recover some data to return to the user. 
  461.              */
  462.             if (stsBytePtr != NULL)
  463.                 *stsBytePtr = PB.scsiSCSIstatus;
  464.             if (msgBytePtr != NULL)
  465.                 *msgBytePtr = scsiMsgBuffer[0];
  466.             if (actualTransferCount != NULL)
  467.                 *actualTransferCount = transferSize - PB.scsiDataResidual;
  468.             /*
  469.              * Note: scsiDataRunError is issued if our transfer request was larger
  470.              * or smaller than the actual transfer length. We need to examine the
  471.              * actual transfer sizes to see how to handle this error. This is
  472.              * not necessarily complete or correct. The intent here is to supress
  473.              * the transfer length error when executing Request Sense or other
  474.              * administrative commands with variable-length result blocks.
  475.              */
  476.             if (status == scsiDataRunError
  477.              && writeToDevice == FALSE
  478.              && actualTransferCount != NULL
  479.              && (*actualTransferCount) <= transferSize
  480.              && (*actualTransferCount) > 0)
  481.                 status = noErr;
  482.             /*
  483.              * If the device issued Check Condition and the SCSI Manager
  484.              * was able to retrieve a Request Sense datum, change the
  485.              * error to our private "Check Condition" status.
  486.              */
  487.             if (status == scsiNonZeroStatus
  488.              && (PB.scsiResultFlags & scsiAutosenseValid) != 0)
  489.                  status = statusErr;
  490.              DisposePtr((Ptr) execIOPBPtr);
  491.         }
  492.         return (status);
  493. #undef PB
  494. }
  495.  
  496. static void NextFunction(void) { }    /* Dummy function for AsyncSCSI size    */
  497.  
  498. static Boolean
  499. IsVirtualMemoryRunning(void)
  500. {
  501.         OSErr                        status;
  502.         long                        response;
  503.         
  504.         status = Gestalt(gestaltVMAttr, &response);
  505.         /*
  506.          * VM is active iff Gestalt succeeded and the response is appropriate.
  507.          */
  508.         return (status == noErr && ((response & (1 << gestaltVMPresent)) != 0));
  509. }
  510.  
  511.